/* Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 *
 * Apache doesn't have an API to retrieve query strings and post data 
 * so I borrowed get_query_params(), get_post_data() functions from 
 * "The Apache Modules Book" and added some modifications on them.
 *
 * Created by hattori <hattori@banana-systems.com>
 *                    <hattori@apache.org> (English Only)
 *
 *
 */

#include "httpd.h"
#include "http_config.h"
#include "http_protocol.h"
#include "http_log.h"

#include "http_util.h"
#include "apr_tables.h"
#include "apr_hash.h"

#define POST_CONTENT_TYPE "application/x-www-form-urlencoded"
#define strEQ(str1,str2)   (strcmp(str1,str2) == 0)
#define strNEQ(str1,str2)  (strcmp(str1,str2) != 0)
#define strcEQ(str1,str2)   (strcasecmp(str1,str2) == 0)
#define strcNEQ(str1,str2)   (strcasecmp(str1,str2) != 0)

static apr_hash_t *get_query_params(request_rec *r, const char *query_params, int *num_query_params);
static int get_post_data(request_rec *r, apr_hash_t **post_data, int *num_query_params);

const int maos_get_http_params(request_rec *r, apr_hash_t **query_params, int *num_query_params){

    int rv = OK;

    if(r->method_number == M_GET){
        *query_params = get_query_params(r, r->args, num_query_params);
    }else if(r->method_number == M_POST){
        rv = get_post_data(r, query_params, num_query_params);
    }else{
        *query_params = get_query_params(r, r->args, num_query_params);
    }

    if (*num_query_params < 5){
        // Obviously, missing required parameters.
        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "mod_auth_opensocial : Missing required OAuth params.");
        return HTTP_BAD_REQUEST;
    }

    return rv;
}

const char *maos_get_query_params_in_str(request_rec *r, apr_hash_t *query_params){
    apr_status_t status;
    apr_hash_index_t *index;
    apr_array_header_t *arr;
    apr_ssize_t klen;
    char *key, *val, *query_params_in_str = NULL;
    char **val_ptr;
    int i;

    if( !query_params ){
        return NULL;
    }

    for (index = apr_hash_first(r->pool, query_params);
        index != NULL;
        index = apr_hash_next(index)){

        apr_hash_this(index,
                   (const void **)&key,
                   &klen,
                   (void **)&arr);

        for (i = 0; i < arr->nelts; i++) {
            const char *val = ((const char**)arr->elts)[i];
            if ( query_params_in_str ){
                query_params_in_str = (char *)apr_pstrcat(r->pool, query_params_in_str, "&", key, "=", val, NULL);
            }else{
                query_params_in_str = (char *)apr_pstrcat(r->pool, key, "=", val, NULL);
            }
        }

    }

    return query_params_in_str;
}

static apr_hash_t *get_query_params(request_rec *r, const char *query_params, int *num_query_params)
{
    apr_hash_t *form;
    apr_array_header_t *values;
    char *pair;
    char *eq;
    const char *delim = "&";
    char *last;
    char *query_params_copy;

    if (query_params == NULL) {
        return NULL;
    }

    query_params_copy = (char *)apr_pstrdup(r->pool,query_params);

    form = apr_hash_make(r->pool);

    for (pair = (char *)apr_strtok(query_params_copy, delim, &last);
          pair != NULL;
          pair = (char *)apr_strtok(NULL, delim, &last)){
        for (eq = pair; *eq; ++eq){
            if(*eq == '+'){
                *eq = ' ';
            }
        }
        eq = strchr(pair, '=');
        if (eq) {
              *eq++ = '\0';
              ap_unescape_url(pair);
              ap_unescape_url(eq);
        }
        else
        {
            eq = "";
            ap_unescape_url(pair);
        }

        values = apr_hash_get(form, pair, APR_HASH_KEY_STRING);
        if (values == NULL) {
            values = apr_array_make(r->pool, 1, sizeof(char*));
            apr_hash_set(form, pair, APR_HASH_KEY_STRING, values);
        }
        *((char **)apr_array_push(values)) = (char *)apr_pstrdup(r->pool, eq);
        (*num_query_params)++;

    }
    return form;
}

static int get_post_data(request_rec *r, apr_hash_t **post_data, int *num_query_params)
{
    int bytes, eos;
    apr_size_t count;
    apr_status_t rv;
    apr_bucket_brigade *bbin;
    char *data_in_str = "";
    const char *buf;
    apr_bucket *b;
    apr_bucket *nextb;
    apr_size_t bucket_len;
    const char *clen;

    const char * content_type = apr_table_get(r->headers_in, "Content-Type");

    if ( content_type == NULL || strcNEQ(content_type,POST_CONTENT_TYPE)){
        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
                  "mod_auth_opensocial : Post content-type is not [%s]", POST_CONTENT_TYPE);
        return HTTP_BAD_REQUEST;
    }

    clen = apr_table_get(r->headers_in, "Content-Length");

    if (clen != NULL){
        bytes = strtol(clen, NULL, 0);
        if (bytes >= MAX_PARAM_SIZE) {
            ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
                    "mod_auth_opensocial : Request's data too big (%d bytes; limit %d)",
                    bytes, MAX_PARAM_SIZE);
          return HTTP_REQUEST_ENTITY_TOO_LARGE;
        }
    }
    else
    {
        bytes = MAX_PARAM_SIZE;
    }
    bbin = apr_brigade_create(r->pool, r->connection->bucket_alloc);
    count = 0;
    eos = 0;
    do {
        rv = ap_get_brigade(r->input_filters, bbin, AP_MODE_READBYTES,
                        APR_BLOCK_READ, bytes);
        if (rv != APR_SUCCESS) {
            return HTTP_INTERNAL_SERVER_ERROR;
        }
        for (b = APR_BRIGADE_FIRST(bbin);
               b != APR_BRIGADE_SENTINEL(bbin);
               b = nextb) {
            nextb = APR_BUCKET_NEXT(b);
            if (APR_BUCKET_IS_EOS(b)) {
                eos = 1;
            }
            if (!APR_BUCKET_IS_METADATA(b)) {
                if (b->length != (apr_size_t)(-1)) {
                    count += b->length;
                    if (count > MAX_PARAM_SIZE) {
                        apr_bucket_delete(b);
                    }
                }
            }
            if (count <= MAX_PARAM_SIZE) {
                APR_BUCKET_REMOVE(b);
                rv = apr_bucket_read(b, &buf, &bucket_len, APR_BLOCK_READ);

                if (rv != APR_SUCCESS) {
                    return HTTP_INTERNAL_SERVER_ERROR;
                }else{
                    data_in_str = (char *)apr_pstrcat(r->pool, data_in_str, apr_pstrmemdup(r->pool, buf, bucket_len), NULL);
                }
            }
        }
    }while (!eos);
    if (count > MAX_PARAM_SIZE) {
        ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
                  "mod_auth_opensocial : Request too big (%d bytes; limit %d)",
                  count, MAX_PARAM_SIZE);
        return HTTP_REQUEST_ENTITY_TOO_LARGE;
    }

    if(r->args){
        // since query strings exist, append them to post data and parse all together.
        char *post_data_and_query_strings =
          (char *)apr_pstrcat(r->pool,r->args, "&", data_in_str,NULL);
        *post_data = get_query_params(r, post_data_and_query_strings, num_query_params);
    }else{
        *post_data = get_query_params(r, buf, num_query_params);
    }
    return OK;
}

